using System;
using System.Collections.Generic;
using System.Threading;

using Jeffrey.XFramework.XCrawl;

namespace Jeffrey.XFramework.XManager
{
    public sealed class UriManagement
    {
        public enum UriResult
        {
            Succeeded,
            Failed,
            Scheduled
        }

        private static readonly Object s_SyncRoot = new Object();

        internal UriManagement(UriContainer uc)
            :this(uc, null)
        { }
        internal UriManagement(UriContainer uc, UriContainer prioruc)
        {
            m_UrlStatusTable = new Dictionary<UInt32, UrlStatus>();

            m_Container = uc;
            m_PriorContainer = prioruc;
        }

        internal Int32 GetNext(out String uri)
        {
            uri = null;
            UrlStatus us;

            if (m_PriorContainer != null)
                uri = m_PriorContainer.GetNext();

            if (uri == null)
                uri = m_Container.GetNext();

            if (uri != null)
            {
                UInt32 key = CrawlToolKits.StringHash(uri);
                lock (s_SyncRoot)
                {
                    us = m_UrlStatusTable[key];
                    if (us.m_Result != UriResult.Scheduled)
                        uri = null;
                    else
                        return us.m_Depth;
                }
            }
            return -1;
        }

        internal void Schedule(String url, Boolean isPrior, Int32 depth)
        {
            url = CrawlToolKits.UrlNormalization(url);

            if ( url == String.Empty  ||
                 url.Length >= 0xFFF0 ||
                 url.Length == 0
               )
                return;

            lock (s_SyncRoot)
            {
                UInt32 key = CrawlToolKits.StringHash(url);
                if (m_UrlStatusTable.ContainsKey(key))
                    return;

                UrlStatus us = new UrlStatus();
                us.m_Result = UriResult.Scheduled;
                us.m_Depth  = (Byte)depth;

                m_UrlStatusTable[key] = us;
            }

            if (isPrior && m_PriorContainer != null)
                m_PriorContainer.Schedule(url);
            else
                m_Container.Schedule(url);
        }

        internal void Succeeded(String url)
        {
            UInt32 key = CrawlToolKits.StringHash(url);
            lock (s_SyncRoot)
            {
                UrlStatus us = m_UrlStatusTable[key];
                us.m_Result  = UriResult.Succeeded;
                m_UrlStatusTable[key] = us;

                Interlocked.Increment(ref m_SucceededUri);
            }
        }
        internal void Failed(String url)
        {
            UInt32 key = CrawlToolKits.StringHash(url);
            lock (s_SyncRoot)
            {
                UrlStatus us = m_UrlStatusTable[key];
                us.m_Result  = UriResult.Failed;
                m_UrlStatusTable[key] = us;

                Interlocked.Increment(ref m_FailedUri);
            }
        }

        internal Boolean Initialize()
        {
            m_SucceededUri = 0;
            m_FailedUri    = 0;

            return (m_Container.Initialize() && (m_PriorContainer == null || m_PriorContainer.Initialize()));
        }

        internal void Release()
        {
            m_Container.Release();
            if (m_PriorContainer != null)
                m_PriorContainer.Release();

            m_UrlStatusTable.Clear();

            m_SucceededUri = 0;
            m_FailedUri    = 0;

            GC.Collect();
        }

        public Int32 AlreadyProcessed { get { return m_FailedUri + m_SucceededUri; } }
        public Int32 AlreadySucceeded { get { return m_SucceededUri; } }

        private struct UrlStatus
        {
            internal UriResult m_Result;
            internal Byte      m_Depth;
        }

        private Dictionary<UInt32, UrlStatus> m_UrlStatusTable;

        private UriContainer m_Container;
        private UriContainer m_PriorContainer;

        private Int32 m_SucceededUri;
        private Int32 m_FailedUri;
    }
}
